/*
 * MarkovChain.java
 *
 * Created on 7 de Julho de 2011, 18:36
 *
 */

package intopo.demonstrator.partnerselection;

/**
 * Represents a Markov Chain of the partner selection model.  This class
 * contains the parameters of the partner selection model.  It also
 * contains the statistics of a run of the Markov Chain.
 *
 * @author <a href="mailto:mariano.pedro@gmail.com">Pedro Mariano</a>
 *
 * @version 1.0 7 de Julho de 2011
 */
public class MarkovChain
{
	/**
	 * The partner selection model parameters.
	 */
	final Parameters parameters;
	/**
	 * The current state of the Markov Chain.
	 */
	State state;
	/**
	 * How many transitions have the Markov Chain made.
	 */
	private long time;
	/**
	 * Each position <i>i</i> of the vector contains the elapsed time till
	 * there are <i>i+1</i> acceptable partner combinations in the
	 * combination vector.  The vector length is {@code poolSize}.
	 */
	private long[] timeAcceptableCombination;
	/**
	 * Each position <i>i</i> of the vector contains how many acceptable
	 * combinations where chosen when there are <i>i+1</i> accepable partner
	 * combinations in the combination vector.  The vector length is {@code
	 * poolSize-1}.  When there are {@code poolSize} acceptable combinations
	 * there is no need to track the evolution of the Markov Chain because
	 * it is in an absorbing state.
	 */
	private int[] selectedAcceptableCombinations;
	/**
	 * How many acceptable combinations there are in the combination vector.
	 */
	private int acceptableCombinationsCount;

	public MarkovChain (Parameters parameters)
	{
		this.parameters = parameters;
		this.state = new State (parameters);
		this.timeAcceptableCombination = new long [parameters.getPoolSize ()];
		this.selectedAcceptableCombinations = new int [parameters.getPoolSize () - 1];

		
		int i;
		for (i = parameters.getPoolSize () - 2; i >= 0; i--) {
			this.selectedAcceptableCombinations [i] = 0;
		}
		this.acceptableCombinationsCount = 0;
	}
	public Parameters getParameters ()
	{
		return parameters;
	}
	/**
	 * Get the current state of the Markov-Chain.
	 */
	public State getState ()
	{
		return this.state;
	}
	/**
	 * Return the time this Markov Chain has run.
	 */
	public long getTime ()
	{
		return this.time;
	}
	/**
	 * Return the time taken for this Markov Chain to have the given number
	 * of acceptable combinations in the combination vector.
	 * @param i The number of acceptable combinations.
	 * @return the time taken for this Markov Chain to have the given number
	 * of acceptable combinations in the combination vector.
	 * @throws IndexOutOfBoundsException If the combination vector does not
	 * have the given number of acceptable combinations.
	 */
	public long getTimeAcceptableCombination (int i)
	{
		if (i > acceptableCombinationsCount) {
			throw new java.lang.IndexOutOfBoundsException ();
		}
		if (i == 0) {
			return 0;
		}
		else {
			return this.timeAcceptableCombination [i - 1];
		}
	}
	/**
	 * Return the number of times an acceptable combination was selected
	 * when the combination vector had the given number of acceptable
	 * combinations.
	 * @param n The number of acceptable combinations.
	 */
	public int getSelectedAcceptableCombinations (int n)
	{
		if (n > acceptableCombinationsCount) {
			throw new java.lang.IndexOutOfBoundsException ();
		}
		return this.selectedAcceptableCombinations [n - 1];
	}
	/**
	 * Return the number of acceptable combinations in the combination vector.
	 */
	public int getAcceptableCombinationsCount ()
	{
		return this.acceptableCombinationsCount;
	}
	/**
	 * Perform a transition of the Markov Chain.  This method updates
	 * statistical fields {@code time}, {@code timeAcceptableCombination}
	 * and {@code acceptableCombinationsCount}.
	 */
	public State transit ()
	{
		if (state.isAbsorbing ()) {
			return null;
		}
		int selectedCombination;
		selectedCombination = state.randomCombination (State.supply);
		State newState = state.transit (selectedCombination);
		if (newState != state) {
			if (newState.isAcceptable (selectedCombination)) {
				this.timeAcceptableCombination [acceptableCombinationsCount] = time;
				this.acceptableCombinationsCount++;
			}
		}
		else {
			this.selectedAcceptableCombinations [this.acceptableCombinationsCount - 1]++;
		}
		time++;
		this.state = newState;
		return newState;
	}
	public State transit (State.TransitionTo transition)
	{
		if (state.isAbsorbing ()) {
			return null;
		}
		State newState = state.transit (transition);
		if (newState != null) {
			if (newState != state) {
				if (transition == State.TransitionTo.S4 || transition == State.TransitionTo.S5) {
					this.timeAcceptableCombination [acceptableCombinationsCount] = time;
					this.acceptableCombinationsCount++;
				}
			}
			else {
				this.selectedAcceptableCombinations [this.acceptableCombinationsCount - 1]++;
			}
			time++;
			this.state = newState;
		}
		return newState;
	}
	/**
	 * Return a string containing the statistics of this Markov Chain.
	 */
	public String toString ()
	{
		StringBuilder result = new StringBuilder (2 * parameters.getPoolSize () * 10);
		int i;
		result.append (this.acceptableCombinationsCount);
		for (i = 0; i < this.acceptableCombinationsCount; i++) {
			result.append (' ');
			result.append (this.timeAcceptableCombination [i]);
		}
		for (i = 0; i < this.acceptableCombinationsCount - 1; i++) {
			result.append (' ');
			result.append (this.selectedAcceptableCombinations [i]);
		}
		return result.toString ();
	}
	/**
	 * Enumeration that represents the possible transitions the Markov chain can
	 * perform.
	 */
	enum TransitionTo {
		/**
		 * The Markov Chain stays in the same state.
		 */
		S1,
		/**
		 * An unacceptable combination is selected and a unacceptable
		 * combination is drawn and the remainder goes to an unacceptable
		 * combination.
		 */
		S2,
		/**
		 * An unacceptable combination is selected and an unacceptable combination is drawn and the remainder goes to an acceptable combination.
		 */
		S3,
		/**
		 * An unacceptable combination is selected and an acceptable combination is drawn and the remainder goes to an unacceptable combination.
		 */
		S4,
		/**
		 * An unacceptable combination is selected and an acceptable combination is drawn and the remainder goes to an acceptable combination.
		 */
		S5;
	}
}

// Local Variables:
// mode: java
// mode: flyspell-prog
// ispell-local-dictionary: "british"
// End:
